#ifndef GLPR2_MATRIX_H
#define GLPR2_MATRIX_H

#include "log.h"
#include "types.h"

#include "m_vector.h"

namespace G3 {

template <int N, class T>
class MATRIX {
    public:
        MATRIX ();
        MATRIX (const T M [N * N]);

        MATRIX<N, T>& operator= (const MATRIX<N, T> &M);
        MATRIX<N, T> operator* (const MATRIX<N, T> &M) const;
        MATRIX<N, T> operator+ (const MATRIX<N, T> &M) const;
        MATRIX<N, T> operator- (const MATRIX<N, T> &M) const;
        MATRIX<N, T> operator* (const T &f) const;
        MATRIX<N, T>& operator*= (const T &f);
        MATRIX<N, T>& operator*= (const MATRIX<N, T> &M);
        T& operator[] (const int &id);

        T m [N * N];

//==============================================================================
// Copies a 16 floats array into a matrix class
//==============================================================================
        void Copy (const T M [N * N]);

//==============================================================================
// Zeroes the matrix
//==============================================================================
        void Zero ();

//==============================================================================
// Generates an identity matrix
//==============================================================================
        void GetIdentity ();

        void Translate (const VEC3<T> &t);
        void Scale (const VEC3<T> &t);

        void RotateX (const double Angle);
        void RotateY (const double Angle);
        void RotateZ (const double Angle);
        void Rotate (VEC3<T> &Axis, double Angle);
        void RotateByAxis (const VEC3<T> &Forward, const VEC3<T> &Up, const VEC3<T> &Right);

        void TransformVector (VEC3<T> &Vect) const;
        void TransformPoint (VEC3<T> &Point) const;

        T GetFromTable (int x, int y) const;

        void Dump ();
};

//==============================================================================
// Multiplies two matrices
//==============================================================================
template <int N, class T>
MATRIX<N, T> MultMatrices (const MATRIX<N, T> &M1, const MATRIX<N, T> &M2);

void MultMatrices (float M1 [16], float M2 [16], float Dest [16]);

//==============================================================================
// Applies a matrix to a vector
//==============================================================================
template <int N, class T>
VEC3<T> ApplyMatrix (const VEC3<T> &V, const MATRIX<N, T> &M);

//==============================================================================
// Generates a translation matrix
//==============================================================================
template <int N, class T>
MATRIX<N, T> TranslateMatrix (const MATRIX<N, T> &M, const VEC3<T> &t);

//==============================================================================
// Generates a scale matrix
//==============================================================================
template <int N, class T>
MATRIX<N, T> ScaleMatrix (const MATRIX<N, T>&M, const VEC3<T> &s);

//==============================================================================
// Generates a pitch matrix (rotation around X axis)
//==============================================================================
template <int N, class T>
MATRIX<N, T> PitchMatrix (const MATRIX<N, T> &M, const double Angle);

//==============================================================================
// Generates a yaw matrix (rotation around Y axis)
//==============================================================================
template <int N, class T>
MATRIX<N, T> YawMatrix (const MATRIX<N, T> &M, const double Angle);

//==============================================================================
// Generates a roll matrix (rotation around Z axis)
//==============================================================================
template <int N, class T>
MATRIX<N, T> RollMatrix (const MATRIX<N, T> &M, const double Angle);

//==============================================================================
// Generates a rotation matrix from angles
//==============================================================================
template <int N, class T>
MATRIX<N, T> RotateMatrix (const MATRIX<N, T> &M, const VEC3<T> &Angle);

//==============================================================================
// Generates a rotation matrix from direction vectors
// NOTE: Causes serious deformations when the vectors are not perpendicular or normalized
//==============================================================================
template <int N, class T>
MATRIX<N, T> BuildRotationMatrix (const VEC3<T> &Forward, const VEC3<T> &Up, const VEC3<T> &Right);

//==============================================================================
// Explicit template instantiation
//==============================================================================
extern template class MATRIX<2, float>;
extern template class MATRIX<2, double>;

extern template class MATRIX<3, float>;
extern template class MATRIX<3, double>;

extern template class MATRIX<4, float>;
extern template class MATRIX<4, double>;

extern template MATRIX<3, float> MultMatrices (const MATRIX<3, float> &M1, const MATRIX<3, float> &M2);
extern template MATRIX<3, double> MultMatrices (const MATRIX<3, double> &M1, const MATRIX<3, double> &M2);
extern template MATRIX<4, float> MultMatrices (const MATRIX<4, float> &M1, const MATRIX<4, float> &M2);
extern template MATRIX<4, double> MultMatrices (const MATRIX<4, double> &M1, const MATRIX<4, double> &M2);

extern template VEC3<float> ApplyMatrix (const VEC3<float> &V, const MATRIX<3, float> &M);
extern template VEC3<double> ApplyMatrix (const VEC3<double> &V, const MATRIX<3, double> &M);
extern template VEC3<float> ApplyMatrix (const VEC3<float> &V, const MATRIX<4, float> &M);
extern template VEC3<double> ApplyMatrix (const VEC3<double> &V, const MATRIX<4, double> &M);

extern template MATRIX<3, float> TranslateMatrix (const MATRIX<3, float> &M, const VEC3<float> &t);
extern template MATRIX<3, double> TranslateMatrix (const MATRIX<3, double> &M, const VEC3<double> &t);
extern template MATRIX<4, float> TranslateMatrix (const MATRIX<4, float> &M, const VEC3<float> &t);
extern template MATRIX<4, double> TranslateMatrix (const MATRIX<4, double> &M, const VEC3<double> &t);

extern template MATRIX<3, float> ScaleMatrix (const MATRIX<3, float> &M, const VEC3<float> &s);
extern template MATRIX<3, double> ScaleMatrix (const MATRIX<3, double> &M, const VEC3<double> &s);
extern template MATRIX<4, float> ScaleMatrix (const MATRIX<4, float> &M, const VEC3<float> &s);
extern template MATRIX<4, double> ScaleMatrix (const MATRIX<4, double> &M, const VEC3<double> &s);

extern template MATRIX<3, float> PitchMatrix (const MATRIX<3, float> &M, const double Angle);
extern template MATRIX<3, double> PitchMatrix (const MATRIX<3, double> &M, const double Angle);
extern template MATRIX<4, float> PitchMatrix (const MATRIX<4, float> &M, const double Angle);
extern template MATRIX<4, double> PitchMatrix (const MATRIX<4, double> &M, const double Angle);

extern template MATRIX<3, float> YawMatrix (const MATRIX<3, float> &M, const double Angle);
extern template MATRIX<3, double> YawMatrix (const MATRIX<3, double> &M, const double Angle);
extern template MATRIX<4, float> YawMatrix (const MATRIX<4, float> &M, const double Angle);
extern template MATRIX<4, double> YawMatrix (const MATRIX<4, double> &M, const double Angle);

extern template MATRIX<3, float> RollMatrix (const MATRIX<3, float> &M, const double Angle);
extern template MATRIX<3, double> RollMatrix (const MATRIX<3, double> &M, const double Angle);
extern template MATRIX<4, float> RollMatrix (const MATRIX<4, float> &M, const double Angle);
extern template MATRIX<4, double> RollMatrix (const MATRIX<4, double> &M, const double Angle);

extern template MATRIX<3, float> RotateMatrix (const MATRIX<3, float> &M, const VEC3<float> &Angle);
extern template MATRIX<3, double> RotateMatrix (const MATRIX<3, double> &M, const VEC3<double> &Angle);
extern template MATRIX<4, float> RotateMatrix (const MATRIX<4, float> &M, const VEC3<float> &Angle);
extern template MATRIX<4, double> RotateMatrix (const MATRIX<4, double> &M, const VEC3<double> &Angle);

extern template MATRIX<3, float> BuildRotationMatrix (const VEC3<float> &Forward, const VEC3<float> &Up, const VEC3<float> &Right);
extern template MATRIX<3, double> BuildRotationMatrix (const VEC3<double> &Forward, const VEC3<double> &Up, const VEC3<double> &Right);
extern template MATRIX<4, float> BuildRotationMatrix (const VEC3<float> &Forward, const VEC3<float> &Up, const VEC3<float> &Right);
extern template MATRIX<4, double> BuildRotationMatrix (const VEC3<double> &Forward, const VEC3<double> &Up, const VEC3<double> &Right);

//==============================================================================
// Types
//==============================================================================
typedef MATRIX<2, float>    MATRIX2x2F;
typedef MATRIX<2, double>	MATRIX2x2D;

typedef MATRIX<3, float>    MATRIX3x3F;
typedef MATRIX<3, double>	MATRIX3x3D;

typedef MATRIX<4, float>    MATRIX4x4F;
typedef MATRIX<4, double>	MATRIX4x4D;

}

#endif
